import React, {Component} from 'react'
import {MarkdownPreview} from 'react-marked-markdown';
// import py1_1 from '../static/py1_1.md'
// import marked from 'marked';

class Py26 extends Component {
    constructor(props) {
        super(props);
        this.state = {
           value: `

在采集网站的时候，还会遇到一些比数据显示在浏览器上却抓取不出来更令人沮丧的事
情。也许是向服务器提交自认为已经处理得很好的表单却被拒绝，也许是自己的 IP 地址不
知道什么原因直接被网站封杀，无法继续访问。

这是由于一些堪称最复杂的 bug 还没有解决，不仅因为这些 bug 让人意想不到（程序在一
个网站上可以正常使用，但在另一个看起来完全一样的网站上却用不了），还因为那些网站
有意不让爬虫抓取信息。网站已经把你定性为一个网络机器人直接拒绝，你无法找出原因。
在这本书里，我已经写了一堆方法来处理网站抓取的难点（提交表单，抽取和清理数据，

执行 JavaScript，等等）。这一章将继续介绍更多的知识点，尽管属于不同的主题（HTTP
headers、CSS 和 HTML 表单等），但这些知识点的共同目的都是为了克服网站阻止自动采
集这个障碍。

即使你觉得下面这些内容现在对你没什么用，我还是强烈建议你至少浏览一下。也许有一
天，这一章的内容会帮你解决一个非常复杂的 bug，或者防止那类 bug 发生。

> 12.1　道德规范

在本书前几章里，我介绍过网络数据采集行为的法律灰色地带，以及网络数据采集涉及的
一些道德规范。说实话，从道德角度上说，这一章是我在写这本书时感到最困难的一章。
我自己的网站已经被网络机器人、垃圾邮件生成器、网络爬虫和其他各种不受欢迎的虚拟
访问者骚扰过很多次了，你的网站可能也是一样。既然如此，我为什么还要在这一章教人
们建立更强大的网络机器人呢？

有几个很重要的理由促使我写这一章。
* 在采集那些不想被采集的网站时，其实存在一些非常符合道德和法律规范的理由。比如
我之前的工作就是做网络爬虫，我曾做过一个自动信息收集器，从未经许可的网站上自
动收集客户的名称、地址、电话号码和其他个人信息，然后把采集的信息提交到网站上，
让服务器删除这些客户信息。为了避免竞争，这些网站都会对网络爬虫严防死守。但是，
我的工作要确保公司的客户们都匿名（这些人都是家庭暴力受害者，或者因其他正当理
由想保持低调的人），这为网络数据采集工作创造了极其合理的条件，我很高兴自己有
能力从事这项工作。
* 虽然不太可能建立一个完全“防爬虫”的网站（最起码得让合法的用户可以方便地访问
网站），但我还是希望本章的内容可以帮助人们保护自己的网站不被恶意攻击。在这一
章的内容里，我将指出每一种网络数据采集技术的缺点，你可以利用这些缺点保护自己
的网站。其实，大多数网络机器人一开始都只能做一些宽泛的信息和漏洞扫描，用本章
介绍的几个简单技术就可以挡住 99% 的机器人。但是，它们进化的速度非常快，最好
时刻准备迎接新的攻击。
* 和大多数程序员一样，我从来不相信禁止某一类信息的传播就可以让世界变得更和谐。
学习这一章的内容时，希望你牢记这里演示的许多程序和介绍的技术都不应该在任何一
个网站上使用。不仅因为这么做对网站不好，而且你可能会收到一个停止并终止警告信
（cease-and-desist letter），甚至还有可能发生更糟糕的事情。不过我也不想每次学习新技术
时都警告你一下。好吧，对本书后面的内容——如哲学家阿甘曾说的——“我想说的就是
这些”（That’s all I have to say about that）。

> 12.2　让网络机器人看起来像人类用户

网站防采集的前提就是要正确地区分人类访问用户和网络机器人。虽然网站可以使用很多
识别技术（比如验证码）来防止爬虫，但还是有一些十分简单的方法，可以让你的网络机
器人看起来更像人类访问用户。

> 12.2.1　修改请求头

在第 9 章里，我们曾经用 requests 模块处理网站的表单。 requests 模块还是一个设置请求
头的利器。HTTP 的请求头是在你每次向网络服务器发送请求时，传递的一组属性和配置
信息。HTTP 定义了十几种古怪的请求头类型，不过大多数都不常用。

如果你是一个防范爬虫的网站管理员，你会让哪个请求头访问你的网站呢？

<center>安装 Requests</center>
   
    我们在第 9 章已经安装过 Requests 模块了，如果你还没有装，可以在模块的网站上找
    到下载链接（http://docs.python-requests.org/en/latest/user/install/）和安装方法，或者用
    任意第三方 Python 模块安装器进行安装。
   
请求头可以通过 requests 模块进行自定义。https://www.whatismybrowser.com/ 网站就是一
个非常棒的网站，可以让服务器测试浏览器的属性。我们用下面的程序来采集这个网站的
信息，验证我们浏览器的 cookie 设置：
    
    import requests
    from bs4 import BeautifulSoup

    session = requests.Session()
    headers = {"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5)
                AppleWebKit 537.36 (KHTML, like Gecko) Chrome",
                "Accept":"text/html,application/xhtml+xml,application/xml;
                q=0.9,image/webp,*/*;q=0.8"}
    url = "https://www.whatismybrowser.com/developers/what-http-headers-is-my-browser-sending"
    req = session.get(url, headers=headers)

    bsObj = BeautifulSoup(req.text)
    print(bsObj.find("table",{"class":"table-striped"}).get_text)
 
程序输出结果中的请求头应该和程序中设置的 headers 是一样的。

虽然网站可能会对 HTTP 请求头的每个属性进行“是否具有人性”的检查，但是我发现通
常真正重要的参数就是 User-Agent 。无论你在做什么项目，一定要记得把 User-Agent 属性
设置成不容易引起怀疑的内容，不要用 Python-urllib/3.4 。另外，如果你正在处理一个警
觉性非常高的网站，就要注意那些经常用却很少检查的请求头，比如 Accept-Language 属
性，也许它正是那个网站判断你是个人类访问者的关键。

<center>请求头会改变你观看网络世界的方式</center>
     
    假设你想为一个机器学习的研究项目写一个语言翻译机，却没有大量的翻译文本来测试它的效果。很多大型网站都会为同样的内容提供不同的语言翻译，根据请求头的参数响应网站不同的语言版本。因此，你只要简单地把请求头属性从 Accept-
    Language:en-US 修改成 Accept-Language:fr ，就可以从网站上获得“Bonjour”（法语，你好）这些数据来改善翻译机的翻译效果了（大型跨国企业通常都是好的采集对象）。请求头还可以让网站改变内容的布局样式。例如，用移动设备浏览网站时，通常会看到一个没有广告、Flash 以及其他干扰的简化的网站版本。因此，把你的请求头 User-Agent 改成下面这样，就可以看一个更容易采集的网站了！

        User-Agent:Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X)
        AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257
        Safari/9537.53
   

> 12.2.2　处理cookie

虽然 cookie 是一把双刃剑，但正确地处理 cookie 可以避免许多采集问题。网站会用 cookie
跟踪你的访问过程，如果发现了爬虫异常行为就会中断你的访问，比如特别快速地填写表
单，或者浏览大量页面。虽然这些行为可以通过关闭并重新连接或者改变 IP 地址来伪装
（更多信息请参见第 14 章），但是如果 cookie 暴露了你的身份，再多努力也是白费。

在采集一些网站时 cookie 是不可或缺的。在第 9 章的例子中曾经介绍过，在一个网站上持
续地保持登录状态，需要你在多个页面中保存一个 cookie。一些网站不要求在每次登录时
都获得一个新 cookie，只要保存一个旧的“已登录”的 cookie 就可以访问网站。

如果你在采集一个或者几个目标网站，我建议你检查这些网站生成的 cookie，然后想想
哪一个 cookie 是爬虫需要处理的。有一些浏览器插件可以为你显示访问网站和离开网站
时 cookie 是如何设置的。EditThisCookie（http://www.editthiscookie.com/）就是我最喜欢的
Chrome 浏览器插件之一。

要获得 cookie 的更多信息，请查看 9.5 节，里面的示例代码介绍了使用 requests 模块处理
cookie 的过程。当然，因为 requests 模块不能执行 JavaScript，所以它不能处理很多新式
的跟踪软件生成的 cookie，比如 Google Analytics，只有当客户端脚本执行后才设置 cookie
（或者在用户浏览页面时基于网页事件产生 cookie，比如点击按钮）。为了处理这些动作，
你需要用 Selenium 和 PhantomJS 包。

你可以对任意网站（本例用的是 http://pythonscraping.com）调用 webdriver 的 get_cookie()
方法来查看 cookie：
    
    from selenium import webdriver
    driver = webdriver.PhantomJS(executable_path='<Path to Phantom JS>')
    driver.get("http://pythonscraping.com")
    driver.implicitly_wait(1)
    print(driver.get_cookies())
     
这样就可以获得一个非常典型的 Google Analytics 的 cookie 列表：
     
    [{'value': '1', 'httponly': False, 'name': '_gat', 'path': '/', 'expi
    ry': 1422806785, 'expires': 'Sun, 01 Feb 2015 16:06:25 GMT', 'secure'
    : False, 'domain': '.pythonscraping.com'}, {'value': 'GA1.2.161952506
    2.1422806186', 'httponly': False, 'name': '_ga', 'path': '/', 'expiry
    ': 1485878185, 'expires': 'Tue, 31 Jan 2017 15:56:25 GMT', 'secure':
    False, 'domain': '.pythonscraping.com'}, {'value': '1', 'httponly': F
    alse, 'name': 'has_js', 'path': '/', 'expiry': 1485878185, 'expires':
    'Tue, 31 Jan 2017 15:56:25 GMT', 'secure': False, 'domain': 'pythons
    craping.com'}]
    
你 还 可 以 调 用 delete_cookie() 、 add_cookie() 和 delete_all_cookies() 方 法 来 处 理
cookie。另外，还可以保存 cookie 以备其他网络爬虫使用。下面的例子演示了如何把这些
函数组合在一起：
    
    from selenium import webdriver

    driver = webdriver.PhantomJS(executable_path='<Path to Phantom JS>')
    driver.get("http://pythonscraping.com")
    driver.implicitly_wait(1)
    print(driver.get_cookies())

    savedCookies = driver.get_cookies()

    driver2 = webdriver.PhantomJS(executable_path='<Path to Phantom JS>')
    driver2.get("http://pythonscraping.com")
    driver2.delete_all_cookies()
    for cookie in savedCookies:
        driver2.add_cookie(cookie)

    driver2.get("http://pythonscraping.com")
    driver.implicitly_wait(1)
    print(driver2.get_cookies())
  
在这个例子中，第一个 webdriver 获得了一个网站，打印 cookie 并把它们保存到变量
savedCookies 里。第二个 webdriver 加载同一个网站（技术提示：必须首先加载网站，这
样 Selenium 才能知道 cookie 属于哪个网站，即使加载网站的行为对我们没任何用处），删
除所有的 cookie，然后替换成第一个 webdriver 得到的 cookie。当再次加载这个页面时，两
组 cookie 的时间戳、源代码和其他信息应该完全一致。从 Google Analytics 的角度看，第
二个 webdriver 现在和第一个 webdriver 完全一样。

> 12.2.3　时间就是一切

有一些防护措施完备的网站可能会阻止你快速地提交表单，或者快速地与网站进行交互。
即使没有这些安全措施，用一个比普通人快很多的速度从一个网站下载大量信息也可能让
自己被网站封杀。

因此，虽然多线程程序可能是一个快速加载页面的好办法——让你在一个线程中处理数据
并在另一个线程中加载页面——但是这对编写好的爬虫来说依然是一个恐怖的策略。还是
应该尽量保证一次加载页面加载且数据请求最小化。如果条件允许，尽量为每个页面访问
增加一点儿时间间隔，即使你要增加一行代码：
     
    time.sleep(3)
   
虽然网络数据采集经常会为了获取数据而破坏规则和冲破底线，但是合理控制速度是你不
应该破坏的规则。这不仅是因为过度消耗别人的服务器资源会让你置身于非法境地，而且
你这么做可能会把一个小型网站拖垮甚至下线。拖垮网站是一件不道德的事情：是彻头彻
尾的错误。所以请控制你的采集速度！

> 12.3　常见表单安全措施

许多像 Litmus 之类的测试工具已经用了很多年了，现在仍用于区分网络爬虫和使用浏览器
的人类访问者，这类手段都取得了不同程度的效果。虽然网络机器人下载一些公开的文章
和博文并不是什么大事，但是如果网络机器人在你的网站上创造了几千个账号并开始向所
有用户发送垃圾邮件，就是一个大问题了。网络表单，尤其是那些用于账号创建和登录的
网站，如果被机器人肆意地滥用，网站的安全和流量费用就会面临严重威胁，因此努力限
制网站的接入是最符合许多网站所有者的利益的（至少他们这么认为）。

这些集中在表单和登录环节上的反机器人安全措施，对网络爬虫来说确实是严重的挑战。
当你为那些表单创建自动化机器人时，你会遇到的安全措施不止这些。关于处理表单的更
多信息，请参考第 11 章关于处理验证码和图片处理的内容，以及第 14 章关于请求头和 IP
地址处理的内容。

> 12.3.1　隐含输入字段值

在 HTML 表单中，“隐含”字段可以让字段的值对浏览器可见，但是对用户不可见（除非
看网页源代码）。随着越来越多的网站开始用 cookie 存储状态变量来管理用户状态，在找
到另一个最佳用途之前，隐含字段主要用于阻止爬虫自动提交表单。

用隐含字段阻止网络数据采集的方式主要有两种。第一种是表单页面上的一个字段可以用
服务器生成的随机变量表示。如果提交时这个值不在表单处理页面上，服务器就有理由认
为这个提交不是从原始表单页面上提交的，而是由一个网络机器人直接提交到表单处理页
面的。绕开这个问题的最佳方法就是，首先采集表单所在页面上生成的随机变量，然后再
提交到表单处理页面。

第二种方式是“蜜罐”（honey pot）。如果表单里包含一个具有普通名称的隐含字段（设置
蜜罐圈套），比如“用户名”（username）或“邮箱地址”（email address），设计不太好的网
络机器人往往不管这个字段是不是对用户可见，直接填写这个字段并向服务器提交，这样
就会中服务器的蜜罐圈套。服务器会把所有隐含字段的真实值（或者与表单提交页面的默
认值不同的值）都忽略，而且填写隐含字段的访问用户也可能被网站封杀。

总之，有时检查表单所在的页面十分必要，看看有没有遗漏或弄错一些服务器预先设定好
的隐含字段（蜜罐圈套）。如果你看到一些隐含字段，通常带有较大的随机字符串变量，
那么很可能网络服务器会在表单提交的时候检查它们。另外，还有其他一些检查，用来保
证这些当前生成的表单变量只被使用一次或是最近生成的（这样可以避免变量被简单地存
储到一个程序中反复使用）。

> 12.3.2　避免蜜罐

虽然在进行网络数据采集时用 CSS 属性区分有用信息和无用信息会很容易（比如，通过读
取 id 和 class 标签获取信息），但这么做有时也会出问题。如果网络表单的一个字段通过
CSS 设置成对用户不可见，那么可以认为普通用户访问网站的时候不能填写这个字段，因
为它没有显示在浏览器上。如果这个字段被填写了，就可能是机器人干的，因此这个提交
会失效。

这种手段不仅可以应用在网站的表单上，还可以应用在链接、图片、文件，以及一些可以
被机器人读取，但普通用户在浏览器上却看不到的任何内容上面。访问者如果访问了网站
上的一个“隐含”内容，就会触发服务器脚本封杀这个用户的 IP 地址，把这个用户踢出网
站，或者采取其他措施禁止这个用户接入网站。实际上，许多商业模式就是在干这些事情。
下面的例子所用的网页在 http://pythonscraping.com/pages/itsatrap.html。这个页面包含了两
个链接，一个通过 CSS 隐含了，另一个是可见的。另外，页面上还包括两个隐含字段：
    
    <html>
    <head>
        <title>A bot-proof form</title>
    </head>
    <style>
        body {
            overflow-x:hidden;
        }
        .customHidden {
            position:absolute;
            right:50000px;
        }
    </style>
    <body>
        <h2>A bot-proof form</h2>
        <a href="http://pythonscraping.com/dontgohere" style="display:none;">Go here!</a>
        <a href="http://pythonscraping.com">Click me!</a>
        <form>
            <input type="hidden" name="phone" value="valueShouldNotBeModified"/><p/>
            <input type="text" name="email" class="customHidden"value="intentionallyBlank"/><p/>
            <input type="text" name="firstName"/><p/>
            <input type="text" name="lastName"/><p/>
            <input type="submit" value="Submit"/><p/>
        </form>
    </body>
    </html>
   
这三个元素通过三种不同的方式对用户隐藏：
* 第一个链接是通过简单的 CSS 属性设置 display:none 进行隐藏
* 电话号码字段 name="phone" 是一个隐含的输入字段
* 邮箱地址字段 name="email" 是将元素向右移动 50 000 像素（应该会超出电脑显示器的
边界）并隐藏滚动条

因为 Selenium 可以获取访问页面的内容，所以它可以区分页面上的可见元素与隐含元素。
通过 is_displayed() 可以判断元素在页面上是否可见。

例如，下面的代码示例就是获取前面那个页面的内容，然后查找隐含链接和隐含输入字段：
   
    from selenium import webdriver
    from selenium.webdriver.remote.webelement import WebElement

    driver = webdriver.PhantomJS(executable_path='')
    driver.get("http://pythonscraping.com/pages/itsatrap.html")
    links = driver.find_elements_by_tag_name("a")
    for link in links:
        if not link.is_displayed():
            print("The link "+link.get_attribute("href")+" is a trap")

    fields = driver.find_elements_by_tag_name("input")
    for field in fields:
        if not field.is_displayed():
            print("Do not change value of "+field.get_attribute("name"))
            
Selenium 抓取出了每个隐含的链接和字段，结果如下所示：
   
    The link http://pythonscraping.com/dontgohere is a trap
    Do not change value of phone
    Do not change value of email
 
虽然你不太可能会去访问你找到的那些隐含链接，但是在提交前，记得确认一下那些已经
在表单中、准备提交的隐含字段的值（或者让 Selenium 为你自动提交）。

> 12.4　问题检查表

这一章介绍的大量知识，其实和这本书一样，都是在介绍如何建立一个更像人而不是更像
机器人的网络爬虫。如果你一直被网站封杀却找不到原因，那么这里有个检查列表，可以
帮你诊断一下问题出在哪里。
* 首先，如果你从网络服务器收到的页面是空白的，缺少信息，或其遇到他不符合你预期
的情况（或者不是你在浏览器上看到的内容），有可能是因为网站创建页面的 JavaScript
执行有问题。可以看看第 10 章内容。
* 如果你准备向网站提交表单或发出 POST 请求，记得检查一下页面的内容，看看你想提
交的每个字段是不是都已经填好，而且格式也正确。用 Chrome 浏览器的网络面板（快
捷键 F12 打开开发者控制台，然后点击“Network”即可看到）查看发送到网站的 POST
命令，确认你的每个参数都是正确的。
* 如果你已经登录网站却不能保持登录状态，或者网站上出现了其他的“登录状态”异常，
请检查你的 cookie。确认在加载每个页面时 cookie 都被正确调用，而且你的 cookie 在
每次发起请求时都发送到了网站上。
* 如果你在客户端遇到了 HTTP 错误，尤其是 403 禁止访问错误，这可能说明网站已经把
你的 IP 当作机器人了，不再接受你的任何请求。你要么等待你的 IP 地址从网站黑名单
里移除，要么就换个 IP 地址（可以去星巴克上网，或者看看第 14 章的内容）。如果你
确定自己并没有被封杀，那么再检查下面的内容。
    
    *  确认你的爬虫在网站上的速度不是特别快。快速采集是一种恶习，会对网管的服务
    器造成沉重的负担，还会让你陷入违法境地，也是 IP 被网站列入黑名单的首要原因。
    给你的爬虫增加延迟，让它们在夜深人静的时候运行。切记：匆匆忙忙写程序或收
    集数据都是拙劣项目管理的表现；应该提前做好计划，避免临阵慌乱。
    
    * 还有一件必须做的事情：修改你的请求头！有些网站会封杀任何声称自己是爬虫的
    访问者。如果你不确定请求头的值怎样才算合适，就用你自己浏览器的请求头吧。
    
    *  确认你没有点击或访问任何人类用户通常不能点击或接入的信息（更多信息请查阅
    12.3.2 节）。
    
    *  如果你用了一大堆复杂的手段才接入网站，考虑联系一下网管吧，告诉他们你的目的。
    试试发邮件到 webmaster@< 域名 > 或 admin@< 域名 >，请求网管允许你使用爬虫采
    集数据。管理员也是人嘛！
    
           
           `,
        };
      }
    render() {
        return (
            <div >
                {/* 111111111111111
                <Py112 /> */}
                <MarkdownPreview value={this.state.value}/>
            </div>
        )
    }
}

export default Py26